/*
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package hextd.core;

import hextd.message.MessageFactory.NetworkMessage;
import hextd.message.MessageMap;
import hextd.message.MessageMapList;
import hextd.util.MapIO;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * MapLoaderServer loads maps from the webserver into the gameserver.
 * TODO: if the webserver is not available, it loads the backup-maps from the jar.
 * @author hylke
 */
public class MapLoaderServer extends MapLoader {

	private final URL mapListUrl, mapFetchUrl;
	private URL backupUrl;
	private MessageMapList mapListMessage;

	public MapLoaderServer(URL mapListUrl, URL mapFetchUrl, String backupIndex, URL backupContextUrl) {
		this.mapListUrl = mapListUrl;
		this.mapFetchUrl = mapFetchUrl;
		try {
			backupUrl = new URL(backupContextUrl, backupIndex);
			loadLocalIndex(backupUrl);
		} catch (MalformedURLException ex) {
			Logger.getLogger(MapLoaderServer.class.getName()).log(Level.SEVERE, null, ex);
		}
		fetchRemoteIndex();
	}

	public MessageMapList getMapListMessage() {
		if (mapListMessage == null) {
			fetchRemoteIndex();
		}
		return mapListMessage;
	}

	public void fetchRemoteIndex() {
		if (mapListUrl != null) {
			synchronized (mapListUrl) {
				try {
					URLConnection connection = mapListUrl.openConnection();
					connection.setDoOutput(false);
					InputStream is = connection.getInputStream();
					InputStreamReader isr = new InputStreamReader(is);
					BufferedReader in = new BufferedReader(isr);
					String receivedLine;
					while ((receivedLine = in.readLine()) != null) {
						boolean oldMatches = NetworkMessage.MapListOld.matches(receivedLine);
						boolean newMatches = NetworkMessage.MapList.matches(receivedLine);
						if (newMatches || oldMatches) {
							if (oldMatches) {
								mapListMessage = (MessageMapList) NetworkMessage.MapListOld.instantiate(receivedLine);
							}
							if (newMatches) {
								mapListMessage = (MessageMapList) NetworkMessage.MapList.instantiate(receivedLine);
							}
							
							categories = mapListMessage.getCategories();
							Map<Integer, MapInfo> mapListTemp = mapListMessage.getMapCache();
							for (Entry<Integer, MapInfo> entry : mapListTemp.entrySet()) {
								Integer id = entry.getKey();
								MapInfo cached = mapCache.get(id);
								MapInfo info = entry.getValue();
								if (cached != null && cached.getContent() != null && cached.lastChange == info.lastChange) {
									info.setContent(cached.getContent());
								}
							}
							mapCache = mapListTemp;
						}
					}
					in.close();
					isr.close();
					is.close();
				} catch (IOException ex) {
					Logger.getLogger(MapLoaderServer.class.getName()).log(Level.SEVERE, null, ex);
				}
			}
			System.out.println("MapLoader::fetchIndex: got " + mapCache.size() + " maps.");
		}
	}

	private boolean fetchMap(MapInfo info) {
		if (mapFetchUrl != null) {
			try {
				URLConnection connection = mapFetchUrl.openConnection();
				connection.setDoOutput(true);
				connection.setConnectTimeout(30000);
				OutputStream os = connection.getOutputStream();
				OutputStreamWriter out = new OutputStreamWriter(os);
				out.write(URLEncoder.encode("mapId", "UTF-8") + "=" + URLEncoder.encode("" + info.mapId, "UTF-8") + "&");
				out.close();
				os.close();

				InputStream is = connection.getInputStream();
				InputStreamReader isr = new InputStreamReader(is);
				BufferedReader in = new BufferedReader(isr);

				String receivedLine;
				while ((receivedLine = in.readLine()) != null) {
					if (NetworkMessage.Map.matches(receivedLine)) {
						MessageMap mapMessage = (MessageMap) NetworkMessage.Map.instantiate(receivedLine);
						if (info.mapId == mapMessage.getMapId()) {
							info.setContent(mapMessage.getMap());
							return true;
						}
					}
				}
				in.close();
				isr.close();
				is.close();
			} catch (IOException ex) {
				Logger.getLogger(MapLoaderServer.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
		return false;
	}

	/**
	 * Loads the index of local map files.
	 * @param fileUrl the url of the index file for the local maps
	 * @param contextUrl the context to which the index url is relative.
	 */
	public void loadLocalIndex(URL indexUrl) {
		localMapContextUrl = indexUrl;
		ArrayList<String> levelNames;

		levelNames = MapIO.getLevelIndex(indexUrl);
		for (String levelName : levelNames) {
			tryLoadLocalFile(levelName, indexUrl);
		}

		if (mapListMessage == null) {
			MessageMapList mml = new MessageMapList();
			mapListMessage = (MessageMapList) mml.initFromString(
					MessageMapList.formatMessage(mapListLocal, categories));
		}
	}

	@Override
	public MapInfo getMap(int mapId) {
		MapInfo info = mapCache.get(mapId);
		if (info.getContent() == null) {
			fetchMap(info);
		}
		return info;
	}
}
